package org.hzero.starter.keyencrypt.mvc;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.hzero.starter.keyencrypt.core.Encrypt;
import org.hzero.starter.keyencrypt.core.EncryptContext;
import org.hzero.starter.keyencrypt.core.IEncryptionService;
import org.hzero.starter.keyencrypt.util.EncryptUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.util.Assert;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;

import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestWrapper;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 继承的{@link ServletModelAttributeMethodProcessor}的功能是：通过ServletRequestDataBinder类型的WebDataBinder将应用数据绑定于ServletModelAttributeMethodProcessor（特定的Servlet ModelAttributeMethodProcessor）。
 * 还添加了回退策略，以便从URI模板变量或请求参数实例化模型属性(如果名称与模型属性名称匹配并且存在适当的类型转换策略)。
 * <p>
 * 本类的功能是：配置解析加密参数功能
 *
 * @author xiangyu.qi01@hand-china.com on 2020-02-09.
 */
public class EncryptServletModelAttributeMethodProcessor extends ServletModelAttributeMethodProcessor {

    @Autowired
    IEncryptionService encryptionService;

    public EncryptServletModelAttributeMethodProcessor() {
        // 为true时，有或者没有@ModelAttribute注解则都设置非简单方法参数或者返回值是被认为model属性
        super(false);
    }

    /**
     * 加了{@link Encrypt}加密注解的参数 并且 参数类型非简单类型（8中基础类型的包装类型+Void类型）等的表示需要处理
     *
     * @param parameter the method parameter to check
     * @return 返回true则表示需要处理，返回false则表示不需要处理
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(Encrypt.class) && !BeanUtils.isSimpleProperty(parameter.getParameterType());
    }


    /**
     * This implementation downcasts {@link WebDataBinder} to
     * {@link ServletRequestDataBinder} before binding.
     * <p>
     * 在绑定之前，实现向下将WebDataBinder转化为ServletRequestDataBinder
     * <p>
     * 代码流程：
     * 1. 如果当前请求头上没有标记开启加密，则走父类{@link ServletModelAttributeMethodProcessor}的方法；（加密标记在网关配置yml文件中指定开启）
     * 2. 如果当前请求已经开启加密了，走下面解析加密参数的逻辑
     * 3.
     */
    @Override
    protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
        if (!EncryptContext.isEncrypt()) {
            super.bindRequestParameters(binder, request);
            return;
        }
        ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
        Assert.state(servletRequest != null, "No ServletRequest");
        /**
         * 父类{@link ServletModelAttributeMethodProcessor}的注释上标注了向下转型（downcasts {@link WebDataBinder} to {@link ServletRequestDataBinder}）
         */
        ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;

        /**
         * binder.getTarget()是请求上的参数对象，此处获取该参数类上加了{@link Encrypt}的属性
         */
        Field[] fields = FieldUtils.getFieldsWithAnnotation(binder.getTarget().getClass(), Encrypt.class);
        if (ArrayUtils.isNotEmpty(fields)) {
            // 自定义包装ServletRequest
            ParameterRequestWrapper requestWrapper = new ParameterRequestWrapper(servletRequest);
            /**
             * {@link HandlerMapping}简介：用于定义一个请求和处理对象的映射的接口，
             * 框架默认提供了{@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping} 和
             * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping}两种实现。
             * </p>
             * 拦截器会调用HandlerMapping的实现类进行映射，但是不是必须的。HandlerMapping将始终包装在{@link org.springframework.web.servlet.HandlerExecutionChain}实例中,
             * 可以选择附带一些HandlerInterceptor实例。
             * </p>
             * DispatcherServlet将首先以给定的顺序调用每个HandlerInterceptor的preHandle方法，最后在所有preHandle方法都返回true的情况下调用处理程序本身。
             * </p>
             * 这种映射的参数化能力是该MVC框架的一项强大且不同寻常的功能。例如，可以编写基于会话状态、Cookie状态或许多其他变量的自定义映射。似乎没有其他MVC框架具有同样的灵活性。
             * </p>
             * 注意：实现可以实现org.springFramework.core.Ordered接口，以便能够指定排序顺序，从而指定Dispatcher Servlet应用的优先级。无序实例被视为最低优先级。
             */
            String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
            // 获取uri参数
            Map<String, String> uriVars = (Map<String, String>) servletRequest.getAttribute(attr);
            for (Field field : fields) {
                Encrypt encrypt = field.getAnnotation(Encrypt.class);
                //解密
                String encryptId = merge(requestWrapper.getParameterValues(field.getName()));
                if (StringUtils.isNotEmpty(encryptId)) {
                    // 集合数据，先按逗号切分，再解密，再合并以逗号切分
                    if (Collection.class.isAssignableFrom(field.getType()) || field.getType().isArray()) {
                        requestWrapper.addParameter(field.getName(), Arrays.stream(encryptId.split(",")).map(item -> EncryptUtils.ignoreValue(encrypt, item) ? item : encryptionService.decrypt(item, encrypt.value(), encrypt.ignoreUserConflict())).collect(Collectors.joining(",")));
                    } else {
                        encryptId = EncryptUtils.ignoreValue(encrypt, encryptId) ? encryptId : encryptionService.decrypt(encryptId, encrypt.value(), encrypt.ignoreUserConflict());
                        requestWrapper.addParameter(field.getName(), encryptId);
                    }
                } else if (uriVars != null && uriVars.containsKey(field.getName())) {
                    encryptId = uriVars.get(field.getName());
                    if (encryptId != null && (Collection.class.isAssignableFrom(field.getType()) || field.getType().isArray())) {
                        uriVars.put(field.getName(), Arrays.stream(encryptId.split(",")).map(item -> EncryptUtils.ignoreValue(encrypt, item) ? item : encryptionService.decrypt(item, encrypt.value(), encrypt.ignoreUserConflict())).collect(Collectors.joining(",")));
                    } else {
                        encryptId = EncryptUtils.ignoreValue(encrypt, encryptId) ? encryptId : encryptionService.decrypt(encryptId, encrypt.value(), encrypt.ignoreUserConflict());
                        uriVars.put(field.getName(), encryptId);
                    }
                }
            }
            servletBinder.bind(requestWrapper);
        } else {
            // 参考父类的代码中默认的数据绑定操作
            servletBinder.bind(servletRequest);
        }
    }

    private String merge(String[] parameterValues) {
        if (parameterValues == null) {
            return null;
        }
        if (parameterValues.length == 0) {
            return "";
        }
        StringBuilder sb = new StringBuilder(parameterValues[0]);
        for (int i = 1; i < parameterValues.length; i++) {
            sb.append(',').append(parameterValues[i]);
        }
        return sb.toString();
    }

    public class ParameterRequestWrapper extends ServletRequestWrapper {

        private Map<String, String[]> params = new HashMap<>();

        @SuppressWarnings("unchecked")
        public ParameterRequestWrapper(ServletRequest request) {
            // 将request交给父类，以便于调用对应方法的时候，将其输出，其实父亲类的实现方式和第一种new的方式类似
            super(request);
            //将参数表，赋予给当前的Map以便于持有request中的参数
            this.params.putAll(request.getParameterMap());
        }

        public ParameterRequestWrapper(ServletRequest request, Map<String, Object> extendParams) {
            this(request);
            addAllParameters(extendParams);//这里将扩展参数写入参数表
        }

        @Override
        public String getParameter(String name) {//重写getParameter，代表参数从当前类中的map获取
            String[] values = params.get(name);
            if (values == null || values.length == 0) {
                return null;
            }
            return values[0];
        }

        @Override
        public String[] getParameterValues(String name) {//同上
            return params.get(name);
        }

        public void addAllParameters(Map<String, Object> otherParams) {//增加多个参数
            for (Map.Entry<String, Object> entry : otherParams.entrySet()) {
                addParameter(entry.getKey(), entry.getValue());
            }
        }


        public void addParameter(String name, Object value) {//增加参数
            if (value != null) {
                if (value instanceof String[]) {
                    params.put(name, (String[]) value);
                } else if (value instanceof String) {
                    params.put(name, new String[]{(String) value});
                } else {
                    params.put(name, new String[]{String.valueOf(value)});
                }
            }
        }
    }


}
